//
// Created by hjie on 23-7-16.
//

extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/timestamp.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavutil/time.h>

};
#include <cstring>
#include <string>
#include <iostream>
#include <map>
#include "base/base_util.h"

int main(int argc, const char * argv[])
{
    if (argc < 3)
    {
        return EXIT_FAILURE;
    }
    std::string input_filename  = argv[1];
    std::string output_filename = argv[2];

    AVFormatContext * input_fmt = avformat_alloc_context();
    int result = avformat_open_input(&input_fmt, input_filename.c_str(), NULL, NULL);
    if (result < 0)
    {
        avformat_close_input(&input_fmt);
        avformat_free_context(input_fmt);
        return 0;
    }
    avformat_find_stream_info(input_fmt, NULL);
    av_dump_format(input_fmt, 0, input_filename.c_str(), 0);

    AVFormatContext * output_format_context;
    result = avformat_alloc_output_context2(&output_format_context, NULL, NULL, output_filename.c_str());
    if (result < 0)
    {
        return -1;
    }
    struct StreamInfo
    {
        int m_index;
        AVMediaType m_media_type;
    };
    std::map<int, StreamInfo> stream_map;
    int stream_index = 0;
    for (size_t index = 0; index < input_fmt->nb_streams; index++)
    {
        if (input_fmt->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
            input_fmt->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            AVStream * new_stream = avformat_new_stream(output_format_context, NULL);
            if (!new_stream)
            {
                return -1;
            }
            stream_map[index] = StreamInfo{.m_index = stream_index++, .m_media_type = input_fmt->streams[index]->codecpar->codec_type};
            result = avcodec_parameters_copy(new_stream->codecpar, input_fmt->streams[index]->codecpar);
            if (result < 0)
            {
                return -1;
            }
        }
    }
    av_dump_format(output_format_context, 0, output_filename.c_str(), 1);

    if (!(output_format_context->oformat->flags & AVFMT_NOFILE))
    {
        avio_open(&output_format_context->pb, output_filename.c_str(), AVIO_FLAG_WRITE);
    }
    result = avformat_write_header(output_format_context, NULL);

    int64_t begin_time = av_gettime();
    AVPacket packet;
    while(av_read_frame(input_fmt, &packet) >= 0)
    {
        if (input_fmt->streams[packet.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO ||
            input_fmt->streams[packet.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {

            AVStream * input_stream = input_fmt->streams[packet.stream_index];

            // spend media file time to remuxing
            AVRational std_time_base = {1, AV_TIME_BASE};
            int64_t pts_time = av_rescale_q(packet.pts, input_stream->time_base, std_time_base);
            int64_t now_time = av_gettime();
            if (pts_time > (now_time - begin_time))
            {
                av_usleep(pts_time - (now_time - begin_time));
            }
            int output_stm_index = stream_map[packet.stream_index].m_index;
            packet.stream_index = output_stm_index;
            packet.pts = av_rescale_q_rnd(packet.pts, input_stream->time_base, output_format_context->streams[packet.stream_index]->time_base, AVRounding(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
            packet.dts = av_rescale_q_rnd(packet.dts, input_stream->time_base, output_format_context->streams[packet.stream_index]->time_base, AVRounding(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
            packet.pos = -1;
            result = av_interleaved_write_frame(output_format_context, &packet);
            if (result < 0) {
                fprintf(stderr, "Error muxing packet\n");
                break;
            }
        }
        av_packet_unref(&packet);
    }
    av_write_trailer(output_format_context);

    ReleaseAVFormatCtx(&input_fmt);
    ReleaseAVFormatCtx(&output_format_context);

    return EXIT_SUCCESS;
}